package camid.distribution.broker;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import camid.distribution.interceptor.InvocationInterceptor;
import camid.distribution.protocol.Endpoint;
import camid.distribution.protocol.InvocationDescriptor;
import camid.distribution.protocol.MethodRequestMessage;
import camid.distribution.protocol.MethodResponseMessage;
import camid.distribution.protocol.ResponseStatus;
import camid.infrastructure.Connector;
import camid.infrastructure.SenderQueue;
import camid.services.nodemanager.ForwardingException;
import camid.util.monitoring.JMeterClientLogger;


public class Requestor {

	
	private SenderQueue queue;
	private List<InvocationInterceptor> interceptors;
	
	private static final Logger logger = LogManager.getLogger(Requestor.class.getName());
	
	public Requestor(SenderQueue senderQueue) 
	{
		this.queue = senderQueue;
		this.interceptors = Collections.synchronizedList( 
				new ArrayList<InvocationInterceptor> ());
	}

	public Object request(Endpoint endpoint, InvocationDescriptor descriptor) throws RuntimeException 
	{
		MethodRequestMessage requestMsg = makeRequestMessage(endpoint, descriptor);
		MethodResponseMessage responseMsg = new MethodResponseMessage();
		
		JMeterClientLogger jmLogger = JMeterClientLogger.getInstance();
		
		Serializable response = null;
		boolean forwarded = false;
		Long startTime = 0L;
		try
		{
			synchronized(interceptors)
			{
				for(InvocationInterceptor interceptor : interceptors)
				{
					interceptor.beforeInvocation(requestMsg);
				}
			}
			
			Connector sender = new Connector(endpoint);
			
			startTime = System.currentTimeMillis();
			responseMsg = queue.doSend(sender, requestMsg);
			
			
			
		}
		catch (Exception e) 
		{
//			throw new RuntimeException(e);
			responseMsg.setResponse(e);
			responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
		}
		
		if(responseMsg != null)
		{
			response = responseMsg.getResponse();
			if(response != null && response instanceof ForwardingException)
			{
				forwarded = true;
			}
		}
		
		if(forwarded)
		{
			logger.info("Downtime (" + descriptor.getEntityClass() + "/" + descriptor.getObjectId() + "/"
					+ descriptor.getMethod() + ") => " + (System.currentTimeMillis() - startTime));
			jmLogger.info("Downtime (" + descriptor.getEntityClass() + "/" + descriptor.getObjectId() + "/"
					+ descriptor.getMethod() + ") => " + (System.currentTimeMillis() - startTime));
		}
		
		try 
		{
			synchronized(interceptors)
			{
				for(InvocationInterceptor interceptor : interceptors)
				{
					interceptor.afterInvocation(responseMsg);
				}
			}
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
		
		adjustEndpoint(endpoint,responseMsg);
		
		
		
		response = responseMsg.getResponse();
		if(response instanceof Exception)
		{
			throw new RuntimeException((Exception)response);
		}
		
		
		return response;
	}

	private void adjustEndpoint(Endpoint endpoint,
			MethodResponseMessage responseMsg) {
	
		if( responseMsg != null)
		{
			Endpoint responseEp = responseMsg.getEndpoint();
			if(endpoint != null && responseEp != null)
			{
				endpoint.setHostname(responseEp.getHostname());
				endpoint.setPort(responseEp.getPort());
			}
		}
	}

	private MethodRequestMessage makeRequestMessage(Endpoint endpoint,
			InvocationDescriptor descriptor) {
		
		MethodRequestMessage requestMsg = new MethodRequestMessage();
		requestMsg.setDescriptor(descriptor);
		requestMsg.setVmId("0");
		
		return requestMsg;
	}
	
	public void addInterceptor(InvocationInterceptor interceptor) 
	{
		this.interceptors.add(interceptor);	
	}

	public void shutdown() 
	{
		this.queue.shutdown();	
	}

}
